#pragma once
#include <iostream>
#include <string>
#include <unordered_map>
#include <vector>
#include <fstream>
#include <mutex>
#include "util.hpp"
namespace ns_index
{
    static std::mutex mtx; // 全局互斥量
    struct word_cnt
    {
        // 词频统计
        int title_cnt;
        int content_cnt;
    };

    struct DockInFo
    {
        // 正排索引的结构体
        std::string title;
        std::string content;
        std::string url;
        uint64_t doc_id;
    };

    struct InvertedElem
    {
        // 倒排索引的结构体
        uint64_t doc_id;
        std::string word;
        int weight;
    };
    typedef std::vector<InvertedElem> InvertedList;

    class index
    {
    private:
        // 静态的index指针
        static index *ind;

        // 防止拷贝构造和赋值构造-确保全局只有一份索引数据
        index(const ns_index::index &in) = delete;
        index &operator=(const ns_index::index &in) = delete;

    public:
        // 创建index指针对象（单例模式）
        static index *CreateIndex()
        {
            if (ind == nullptr) // 过滤，防止加锁的资源消耗
            {
                mtx.lock();
                if (ind == nullptr) // 在锁外，可以在一个创建完成后，其他无法创建（保证唯一性）
                {
                    ind = new index;
                }
                mtx.unlock();
            }
            return ind;
        }

    private:
        // 正排索引 使用下标作为id的形式
        std::vector<DockInFo> forward_index;
        // 倒排索引 使用kv模型
        std::unordered_map<std::string, InvertedList> inverted_list;

        index(){};
        ~index(){};

    public:
        // 根据正排索引获取文档内容
        DockInFo *GetForwardIndex(const uint64_t &doc_id)
        {
            // 超出数组的doc_id就是文档中没用的
            if (doc_id >= forward_index.size())
            {
                std::cerr << "doc_id out range,error!" << std::endl;
                return nullptr;
            }
            return &forward_index[doc_id];
        }

        // 根据倒排索引关键字获取doc_id
        InvertedList *GetInvertedList(std::string &word)
        {
            auto it = inverted_list.find(word);
            if (it == inverted_list.end())
            {
                std::cerr << word << " No find" << std::endl;
                return nullptr;
            }
            return &(it->second);
        }

        bool BuildIndex(const std::string &input)
        {
            // 根据取标签的文档内容构建正排倒排索引

            // 打开文件
            std::ifstream re(input, std::ios::binary | std::ios::in);
            if (!re)
            {
                std::cerr << input << " open err" << std::endl;
                return false;
            }

            // 先构建正排索引
            std::string line;
            std::cout << "开始构建索引" << std::endl;
            int n = 0;
            while (std::getline(re, line))
            {
                DockInFo *doc = BuildForwardIndex(line);
                if (doc == nullptr)
                {
                    std::cerr << "BuildForwardIndex err:" << line << std::endl;
                    continue;
                }

                // 根据正排索引的文本 构建倒排索引
                if (!BuildInvertedIndex(doc))
                {
                    std::cerr << "BuildInvertedIndex err" << doc << std::endl;
                    continue;
                }
                n++;
                if (n % 500 == 0)
                {
                    std::cout << "构建索引：" << n << std::endl;
                }
            }
            return true;
        }

    private:
        DockInFo *BuildForwardIndex(std::string &line)
        {
            // 创建vector保存切割的每一部分，按下标区分
            std::vector<std::string> results;
            const std::string sep = "\3";
            ns_ntil::StringUtil::CutString(line, &results, sep);
            if (results.size() < 3)
            {
                return nullptr;
            }
            // 把切开的标题等元素放入DockInFo正排索引结构体
            DockInFo doc;
            doc.title = results[0];
            doc.content = results[1];
            doc.url = results[2];
            doc.doc_id = forward_index.size();
            // 插入正排索引
            forward_index.push_back(std::move(doc));
            // 返回刚刚插入的DockInFo结构体
            return &forward_index.back();
        }

        bool BuildInvertedIndex(DockInFo *doc)
        {
            if (doc == nullptr)
            {
                std::cerr << "DockInFo is nullptr" << std::endl;
                return false;
            }

            // 分词 使用cppjieba
            // title分词
            std::vector<std::string> word_title;
            ns_ntil::JiebaUtil::CutString(doc->title, &word_title);
            // content分词
            std::vector<std::string> word_content;
            ns_ntil::JiebaUtil::CutString(doc->content, &word_content);

            // 使用unordered_map对词出现的次数进行统计 word_cnt是词频统计结构体
            std::unordered_map<std::string, word_cnt> word_cnt;
            // 对title进行统计
            for (std::string &s : word_title)
            {
                // 使用哈希可以把文档所有的词插入进去并统计出现次数
                // 分词统计时把大小写全部统计为小写，便于搜索
                boost::to_lower(s);
                word_cnt[s].title_cnt++;
            }
            // 对content进行统计
            for (std::string &s : word_content)
            {
                // 使用哈希可以把文档所有的词插入进去并统计出现次数
                boost::to_lower(s);
                word_cnt[s].content_cnt++;
            }

            // 构建倒排拉链
            for (auto &word : word_cnt)
            {
                InvertedElem elem;
                elem.doc_id = doc->doc_id;
                elem.word = word.first;
                elem.weight = 10 * word.second.title_cnt + word.second.content_cnt;

                // 构建倒排索引
                InvertedList &invertedlist = inverted_list[word.first];
                invertedlist.push_back(std::move(elem));
            }
            return true;
        }
    };
    index *index::ind = nullptr;
}
